<?php
namespace FCLiteHelper;

use Exception;

class Response {
  public $statusCode = 200;
  public $headers = [];
  public $body = '';
  public $isBase64Encoded = false;

  /**
   * Add header
   * @param string $key Header key
   * @param string $value Header value
   * @param bool $replace Replace exists header
   */
  public function header($key, $value, $replace = false) {
    if ($replace) {
      $this->headers[$key] = $value;
      return;
    }
    if (isset($this->headers[$key])) {
      if (!is_array($this->headers[$key])) {
        $curValue = $this->headers[$key];
        $this->headers[$key] = [];
        $this->headers[$key][] = $curValue;
      }
      $this->headers[$key][] = $value;
    } else {
      $this->headers[$key] = $value;
    }
  }

  /**
   * Add cookie
   * @see setcookie
   */
  public function cookie($name, $value, $options) {
    $v = [];
    $v[] = urlencode($name) . '=' . urlencode($value);
    if (isset($options['expires'])) {
      $v[] = 'Expires=' . gmdate('D, d M Y H:i:s T', $options['expires']);
    }
    if (isset($options['domain'])) {
      $v[] = 'Domain=' . urlencode($options['domain']);
    }
    if (isset($options['path'])) {
      $v[] = 'Path=' . $options['path'];
    }
    if (isset($options['sameSite'])) {
      $v[] = 'SameSite=' . $options['sameSite'];
    }
    if (isset($options['secure'])) {
      $v[] = 'Secure';
    }
    if (isset($options['httpOnly'])) {
      $v[] = 'HttpOnly';
    }
    $this->header('set-cookie', implode('; ', $v), false);
  }
}

abstract class BaseAdapter {
  abstract static function is();

  /** @var Response response object */
  public $response;

  protected $evt;
  protected $_body;

  public function __construct($evt) {
    $this->evt = $evt;
    $this->response = new Response();
  }

  /**
   * Get request headers
   * @return array
   */
  abstract public function header();

  /**
   * Get parsed query
   * @return array
   */
  abstract public function query();

  /**
   * Get raw body
   * @return string
   */
  abstract public function rawBody();

  /**
   * Get parsed body
   * @return string/array
   */
  public function body() {
    if (empty($this->_body)) {
      $this->_body = AdapterUtils::parseBody($this->header()['content-type'], $this->rawBody());
    }
    return $this->_body;
  }

  /**
   * Get request method
   * @return string
   */
  abstract public function method();
  
  /**
   * Get request path
   * @return string
   */
  abstract public function path();
  
  /**
   * Get request domain
   * @return string
   */
  abstract public function domain();
  
  /**
   * Get response object
   */
  abstract public function getResponse();
}


class AdapterUtils {
  static function parseMultiPart($contentType, $body) {
    preg_match('/boundary=(?:"([^"]+)"|([^;]+))/i', $contentType, $matches);
  
    if (!$matches) {
      throw new Exception('Bad content-type header, no multipart boundary');
    }
  
    // \r\n is part of the boundary.
    $boundary = "\r\n--" . ($matches[1] || $matches[2]);
  
    // Prepend what has been stripped by the body parsing mechanism.
    $parts = explode($boundary, "\r\n" . $body);
    $partsByName = [];
  
    // First part is a preamble, last part is closing '--'
    foreach ($parts as $part) {
      $contentStartPos = strpos($part, "\r\n\r\n") + 4;
      $partHeaders = explode("\r\n", substr($part, 0, $contentStartPos - 4));
      $partContent = substr($part, $contentStartPos);
      $fieldName = '';
      foreach ($partHeaders as $headerItem) {
        $matchResult = null;
        preg_match('/^.*name="([^"]*)"$/', $headerItem, $matchResult);
        if ($matchResult) {
          $fieldName = $matchResult[1];
          break;
        }
      }
      if (!empty($fieldName)) {
        $partsByName[$$fieldName] = $partContent;
      }
    }
  
    return $partsByName;
  }
  
  static function parseBody($contentType, $rawBody) {
    try {
      if (strpos($contentType, 'application/json') === 0) {
        return json_decode($rawBody, true);
      }
      if (strpos($contentType, 'application/x-www-form-urlencoded') === 0) {
        $result = null;
        parse_str($rawBody, $result);
        return $result;
      }
      if (strpos($contentType, 'multipart/form-data') === 0) {
        return self::parseMultiPart($contentType, $rawBody);
      }
    } catch (\Throwable $e) {
      // ignore
    }
    return $rawBody;
  }

  static function parseHeaders($origin) {
    $header = [];
    foreach ($origin as $key => $value) {
      $header[strtolower($key)] = $value;
    }
    return $header;
  }
}

class AlibabaCloudAdapter extends BaseAdapter {
  static function is() {
    return !empty(getenv('ALIBABA_CLOUD_ACCESS_KEY_ID'));
  }

  private $_header;
  private $_rawBody;

  public function header() {
    if (empty($this->_header)) {
      $this->_header = AdapterUtils::parseHeaders($this->evt['headers']);
    }
    return $this->_header;
  }

  public function query() {
    return $this->evt['queryParameters'];
  }

  public function rawBody() {
    if (empty($this->_rawBody)) {
      if ($this->evt['isBase64Encoded']) {
        $this->_rawBody = base64_decode($this->evt['body']);
      } else {
        $this->_rawBody = $this->evt['body'];
      }
    }
    return $this->_rawBody;
  }

  public function method() {
    return $this->evt['requestContext']['http']['method'];
  }

  public function path() {
    return $this->evt['requestContext']['http']['path'];
  }

  public function domain() {
    return $this->evt['requestContext']['domainName'];
  }

  /** Get response */
  public function getResponse() {
    $headers = [];
    foreach ($this->response->headers as $key => $value) {
      $headers[$key] = is_array($value) ? implode(',', $value) : $value;
    }
    return [
      'statusCode' => $this->response->statusCode,
      'headers' => $headers,
      'body' => $this->response->body,
      'isBase64Encoded' => $this->response->isBase64Encoded,
    ];
  }
}


class TencentCloudAdapter extends BaseAdapter {
  static function is() {
    return !empty(getenv('TENCENTCLOUD_RUNENV'));
  }

  private $_header;

  public function header() {
    if (empty($this->_header)) {
      $this->_header = AdapterUtils::parseHeaders($this->evt['headerParameters']);
    }
    return $this->_header;
  }

  public function query() {
    return $this->evt['queryStringParameters'];
  }

  public function rawBody() {
    return $this->evt['body'];
  }

  public function method() {
    return $this->evt['requestContext']['httpMethod'];
  }

  public function path() {
    return $this->evt['requestContext']['path'];
  }

  public function domain() {
    return $this->evt['requestContext']['domainName'];
  }

  /** Get response */
  public function getResponse() {
    return [
      'statusCode' => $this->response->statusCode,
      'headers' => $this->response->headers,
      'body' => $this->response->body,
      'isBase64Encoded' => $this->response->isBase64Encoded,
    ];
  }
}

function createContext($evt): BaseAdapter {
  if (AlibabaCloudAdapter::is()) {
    return new AlibabaCloudAdapter($evt);
  }
  if (TencentCloudAdapter::is()) {
    return new TencentCloudAdapter($evt);
  }
  throw new Exception('No available adapter');
}